﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
using FiniteStateMachine;
using Microsoft.CSharp;

namespace Workflows.Components.Rules
{
    /// <summary>
    /// convert Rule to FSM
    /// </summary>
    public class RuleParser
    {
        private readonly char[] _AllAvailableRuleLabels = new char[]
            {   '_',
                'A', 'B', 'C', 'D', 'E', 
                'F', 'G', 'H', 'I', 'J', 
                'K', 'L', 'M', 'N', 'O', 
                'P', 'Q', 'R', 'S', 'T', 
                'U', 'V', 'W', 'X', 'Y', 
                'Z', '$'
            };
        private readonly Rule _Rule;
        private string _ClassCodeTemplate;
        private string _ClassCode;
        private Dictionary<Guid, char> _RuleConditionLabels;
        private Dictionary<char, RuleEngine.EvaluationResult> _RuleConditionEvaluationResult;
        private Dictionary<int, string> _AllAvailableStates;
        private FiniteStateMachineBase _FSM;
        /// <summary></summary>
        public FiniteStateMachineBase StateMachine
        {
            get
            {
                return this._FSM;
            }
            set
            {
                this._FSM = value;
            }
        }

        /// <summary></summary>
        public string StateName
        {
            get
            {
                return "RuleState";
            }
        }

        ///<summary>
        ///</summary>
        public string StateMachineName
        {
            get
            {
                return "StateMachine";
            }
        }

        /// <summary>
        /// class code with place holder to be replaced by code snippet user entered
        /// </summary>
        public string ClassCodeTemplate
        {
            get
            {
                return this._ClassCodeTemplate;
            }
        }

        /// <summary>
        /// class code updated with code snippet user entered
        /// </summary>
        public string ClassCode
        {
            get
            {
                return this._ClassCode;
            }
        }

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="rule"></param>
        public RuleParser(Rule rule)
        {
            this._Rule = rule;
            this._ClassCodeTemplate = string.Empty;
            this._FSM = this.Parse(this._Rule);
        }


        #region parse
        /// <summary>
        /// 
        /// </summary>
        /// <param name="rule"></param>
        /// <returns></returns>
        private FiniteStateMachineBase Parse(Rule rule)
        {
            this._RuleConditionLabels = new Dictionary<Guid, char>();
            this._RuleConditionEvaluationResult = new Dictionary<char, RuleEngine.EvaluationResult>();

            int seq = 0;
            if (rule.RuleNodes != null && rule.RuleNodes.Length > 0)
            {
                foreach (RuleNodeDataBase ruleData in rule.RuleNodes)
                {
                    if (ruleData.RuleNodeType == RuleNodeType.Conditional)
                    {
                        seq++;
                        char ruleLabel = this._AllAvailableRuleLabels[seq];
                        RuleEngine.EvaluationResult evaluateResult = RuleEngine.EvaluationResult.NotDefined;
                        this._RuleConditionLabels.Add(ruleData.RuleNodeID, ruleLabel);
                        this._RuleConditionEvaluationResult.Add(ruleLabel, evaluateResult);
                    }
                }
            }

            List<string> statePaths = new List<string>();
            string firstState = "_";
            statePaths.Add(firstState);
            if (rule.RuleNodes != null && rule.RuleNodes.Length > 0)
            {
                this.WalkRuleNodes(rule.RuleNodes, rule.Start, firstState, rule.Finish, ref statePaths);
            }
            this._AllAvailableStates = new Dictionary<int, string>();
            int lastStateNumber = 0;
            foreach (string path in statePaths)
            {
                lastStateNumber++;
                this._AllAvailableStates.Add(lastStateNumber, path);
            }
            // generate enum to represent states
            string stateEnumCode = this.CreateStateEnumCodeSnippet(this._AllAvailableStates);
            // event classes
            Dictionary<string, string> eventNameCode = this.CreateEventClasses(statePaths);
            // method implementation to handle events
            Dictionary<string, string> codeSnippetPlaceHolders = new Dictionary<string, string>();
            Dictionary<string, string> methodNameCode = this.CreateHandlerMethods(statePaths, ref codeSnippetPlaceHolders);

            StringBuilder codeTemplate = new StringBuilder();
            

            codeTemplate.Append("using System;\n");
            codeTemplate.Append("using System.Collections.Generic;\n");
            codeTemplate.Append("using System.Diagnostics;\n");
            codeTemplate.Append("using System.Reflection;\n");
            codeTemplate.Append("using System.Text;\n");
            codeTemplate.Append("using System.Windows.Forms;\n");
            codeTemplate.Append("using FiniteStateMachine;\n");
            codeTemplate.Append("\n");

            codeTemplate.Append("namespace " + this.GetType().Namespace + "{\n");
            

            codeTemplate.Append("public class " + this.StateMachineName + ":FiniteStateMachineBase {\n");
            // put event class within class scope
            codeTemplate.Append("\n");

            foreach (string eventName in eventNameCode.Keys)
            {
                string eventCode = eventNameCode[eventName];
                codeTemplate.Append("\n\n// event " + eventName + "\n");
                codeTemplate.Append(eventCode);
            }

            // put state enum and property here
            codeTemplate.Append("\n");
            codeTemplate.Append(stateEnumCode + "\n");

            // ctor
            codeTemplate.Append("public " + this.StateMachineName + "(){\n");
            codeTemplate.Append("\tthis.State = RuleState." + firstState + ";");
            codeTemplate.Append("}\n\n");

            // init
            codeTemplate.Append("\tprotected override void OnFsmEntry() {\n");
            codeTemplate.Append("\t RuleResult_" + firstState + " evt=new RuleResult_" + firstState + "();\n");
            codeTemplate.Append("\t this.PushEvent(evt);\n");
            codeTemplate.Append("}\n");

            // put methods here
            foreach(string methodName in methodNameCode.Keys)
            {
                string methodCode = methodNameCode[methodName];
                codeTemplate.Append("\n\n// method " + methodName + "\n");
                codeTemplate.Append(methodCode);
            }

            codeTemplate.Append("}\n"); // end of class
            codeTemplate.Append("}\n"); // end of namespace 

            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters param = new CompilerParameters();
            param.GenerateExecutable = false;
            param.IncludeDebugInformation = false;
            param.GenerateInMemory = true;
            param.ReferencedAssemblies.Add("System.dll");
            param.ReferencedAssemblies.Add("System.Configuration.dll");
            param.ReferencedAssemblies.Add("System.Data.dll");
            param.ReferencedAssemblies.Add("System.Xml.dll");
            param.ReferencedAssemblies.Add("System.Drawing.dll");
            param.ReferencedAssemblies.Add("System.Web.dll");
            param.ReferencedAssemblies.Add("System.Windows.Forms.dll");
            Assembly fsmAssembly = (typeof (FiniteStateMachineBase)).Assembly;
            param.ReferencedAssemblies.Add(fsmAssembly.Location);

            CompilerResults cr = provider.CompileAssemblyFromSource(param, codeTemplate.ToString());
            StringBuilder buffer = new StringBuilder();
            StringCollection output = cr.Output;
            if (cr.Errors.Count != 0)
            {
                Console.WriteLine("Error invoking scripts.");
                CompilerErrorCollection es = cr.Errors;
                foreach (CompilerError s in es)
                {
                    buffer.Append(s.ErrorText + Environment.NewLine);
                }
                Console.Write(output);
                throw new Exception(buffer.ToString());
            }
            Assembly compiledAssembly = cr.CompiledAssembly;
            if (compiledAssembly != null)
            {
                string className = this.GetType().Namespace + "." + this.StateMachineName;
                object instance =
                    compiledAssembly.CreateInstance(className);
                this._ClassCodeTemplate = codeTemplate.ToString();
                this._ClassCode = this._ClassCodeTemplate;
                if(codeSnippetPlaceHolders.Count>0)
                {
                    foreach(string placeHolder in codeSnippetPlaceHolders.Keys)
                    {
                        this._ClassCode = this._ClassCode.Replace(placeHolder, codeSnippetPlaceHolders[placeHolder]);
                    }
                }
                return instance as FiniteStateMachineBase;
            }
            else
            {
                return null;
            }
        }

        private void WalkRuleNodes(RuleNodeDataBase[] allNodes, RuleNodeDataBase fromNode, string currentStatePath, RuleNodeDataBase endNode, ref List<string> paths)
        {
            foreach (RuleNodeDataBase node in allNodes)
            {
                if (node.FromNodeID == fromNode.RuleNodeID)
                {
                    if (node.RuleNodeType == RuleNodeType.Conditional)
                    {
                        bool canContinue = false;
                        if (string.IsNullOrEmpty(currentStatePath))
                        {
                            canContinue = true;
                        }
                        else
                        {
                            string lastStateLabel = currentStatePath.Substring(currentStatePath.Length - 1, 1);
                            bool isLastStateTrue = true;
                            if (lastStateLabel == lastStateLabel.ToLower())
                            {
                                isLastStateTrue = false;
                            }
                            if (node.FromNodeBranch == string.Empty)
                            {
                                canContinue = true;
                            }
                            if (node.FromNodeBranch.ToLower() == "true" && isLastStateTrue)
                            {
                                canContinue = true;
                            }
                            else if (node.FromNodeBranch.ToLower() == "false" && !isLastStateTrue)
                            {
                                canContinue = true;
                            }
                        }
                        if (canContinue)
                        {
                            string truePath = currentStatePath + this._RuleConditionLabels[node.RuleNodeID].ToString();
                            paths.Add(truePath);
                            this.WalkRuleNodes(allNodes, node, truePath, endNode, ref paths);
                            string falsePath = currentStatePath +
                                               this._RuleConditionLabels[node.RuleNodeID].ToString().ToLower();
                            paths.Add(falsePath);
                            this.WalkRuleNodes(allNodes, node, falsePath, endNode, ref paths);
                        }
                    }
                    else
                    {
                        this.WalkRuleNodes(allNodes, node, currentStatePath, endNode, ref paths);
                    }
                }
            }
        }

        #endregion

        #region states
        private string CreateStateEnumCodeSnippet(Dictionary<int,string> states)
        {
            StringBuilder buffer=new StringBuilder();
            buffer.Append("public enum " + this.StateName + " {\n");
            int[] stateNumbers=new int[states.Count];
            states.Keys.CopyTo(stateNumbers,0);
            for (int i = 0; i < stateNumbers.Length; i++)
            {
                buffer.Append("\t" + states[stateNumbers[i]] + "=" + stateNumbers[i]);
                if(i<stateNumbers.Length -1)
                {
                    buffer.Append(",\n");
                }
            }
            buffer.Append("}\n");

            buffer.Append("private " + this.StateName + " _State;\n");
            buffer.Append("public " + this.StateName + " State {\n");
            buffer.Append("\tget{ return this._State;}\n");
            buffer.Append("\tset{ this._State=value; }\n");
            buffer.Append("}\n");
            buffer.Append("protected override int GetCurrentState(){\n" +
                          "\treturn (int)this._State;\n" +
                          "}\n");
            return buffer.ToString();
        }
        #endregion

        #region events
        private Dictionary<string,string> CreateEventClasses(List<string> statePaths)
        {
            Dictionary<string,string> eventClassNameAndCode=new Dictionary<string, string>();
            statePaths.Sort();
            foreach(string statePath in statePaths)
            {
                StringBuilder buffer=new StringBuilder();
                buffer.Append("public class RuleResult_" + statePath + " : FsmEvent {\n");
                buffer.Append("}");
                eventClassNameAndCode.Add("RuleResult_" + statePath, buffer.ToString());
            }
            return eventClassNameAndCode;
        }
        #endregion

        #region evaluator
        private Dictionary<string,string> CreateHandlerMethods(List<string> statePaths, ref Dictionary<string,string> codeSnippetPlaceHolders)
        {
            Dictionary<string,string> methodNameCode=new Dictionary<string, string>();
            statePaths.Sort();
            string firstState = "_";
            foreach (string currentState in statePaths)
            {
                // if(currentState !=firstState)
                RuleNodeDataBase nextConditionNode = null;
                string prevStatePath = string.Empty;
                char label = '_';
                if (currentState != firstState)
                {
                    bool foundNode = false;
                    foreach (RuleNodeDataBase ruleNode in this._Rule.RuleNodes)
                    {
                        if (ruleNode.RuleNodeType == RuleNodeType.Conditional)
                        {
                            RuleNodeDataBase ruleCondition = ruleNode;
                            label = this._RuleConditionLabels[ruleCondition.RuleNodeID];

                            for (int i = 0; i < statePaths.Count; i++)
                            {
                                if (statePaths[i].ToLower().EndsWith(label.ToString().ToLower()) &&
                                    statePaths[i].Length == currentState.Length)
                                {
                                    prevStatePath = statePaths[i].Substring(0, statePaths[i].Length - 1);
                                    nextConditionNode = ruleCondition;
                                    foundNode = true;
                                    break;
                                }
                            }
                        }
                        if(foundNode)
                            break;
                    }
                }
                if (nextConditionNode != null && !string.IsNullOrEmpty(prevStatePath))
                {
                    string methodName = "EvaluateRule_" + prevStatePath +
                                        label.ToString();
                    string eventClassName = "RuleResult_" + prevStatePath;
                    StringBuilder buffer = new StringBuilder();
                    buffer.Append("[FsmEventHandler]\n");
                    buffer.Append("private void " + methodName + "(" + eventClassName + " evt) {\n");
                    buffer.Append("\tbool result=false;\n");
                    buffer.Append("\ttry{\n");
                    if (nextConditionNode.GetType() == typeof (DeclarativeRuleCondition))
                    {
                    }
                    else if (nextConditionNode.GetType() == typeof (CodeRuleCondition))
                    {
                        CodeRuleCondition codeCondition = (CodeRuleCondition) nextConditionNode;
                        string placeHolder = string.Format(" // @{0}", nextConditionNode.RuleNodeID.ToString());
                        buffer.Append("\n\n" + placeHolder + "\n\n");
                        if (codeSnippetPlaceHolders.ContainsKey(placeHolder))
                        {
                            codeSnippetPlaceHolders[placeHolder] = codeCondition.CodeSnippet;
                        }
                        else
                        {
                            codeSnippetPlaceHolders.Add(placeHolder, codeCondition.CodeSnippet);
                        }
                    }
                    else if (nextConditionNode.GetType() == typeof (ClassRuleCondition))
                    {
                        // Assembly classAssem = Assembly.LoadFile(((ClassRuleCondition) ruleCondition).EvaluableAssemblyFilePath);
                        buffer.Append(
                            "\t\t\tAssembly classAssem = Assembly.LoadFile(" +
                            ((ClassRuleCondition) nextConditionNode).EvaluableAssemblyFilePath +
                            ");\n");
                        // Type classType = classAssem.GetType(((ClassRuleCondition) ruleCondition).EvaluableTypeName);
                        buffer.Append(
                            "\t\t\tType classType = classAssem.GetType(" +
                            ((ClassRuleCondition) nextConditionNode).EvaluableTypeName +
                            ");");
                        // object classInstance = Activator.CreateInstance(classType);
                        buffer.Append(
                            "\t\t\tobject classInstance = Activator.CreateInstance(classType);");
                        // MethodInfo classMethod = classType.GetMethod(((ClassRuleCondition) ruleCondition).MethodName);
                        buffer.Append(
                            "\t\t\tMethodInfo classMethod = classType.GetMethod(" +
                            ((ClassRuleCondition) nextConditionNode).MethodName +
                            ");");
                        // object classResult = classMethod.Invoke(classInstance, null);
                        buffer.Append("\t\t\tobject classResult = classMethod.Invoke(classInstance, null);");
                        // result = (bool) classResult;
                        buffer.Append("\t\t\tresult = (bool) classResult;");
                    }
                    else
                    {
                        Debug.Assert(false, "Condition type is not supported: " + nextConditionNode.GetType().FullName);
                    }

                    // if evaluate to true, push next event
                    buffer.Append("\t\tif(result) {\n");
                    string truePath = prevStatePath + label.ToString();
                    buffer.Append("\t\t\tthis.State=" + this.StateName + "." + truePath + ";\n");
                    bool isTruePathLast = true;
                    foreach (string statePath in statePaths)
                    {
                        if (statePath.StartsWith(truePath) && statePath.Length > truePath.Length)
                        {
                            isTruePathLast = false;
                            break;
                        }
                    }
                    if (isTruePathLast)
                    {
                        buffer.Append("\t\t\tthis.Done();\n");
                    }
                    else
                    {
                        buffer.Append("\t\t\tRuleResult_" + truePath + " evtNext=new RuleResult_" +
                                      truePath + "();\n");
                        buffer.Append("\t\t\tthis.PushEvent(evtNext);\n");
                    }
                    buffer.Append("\t\t}\n"); // end if block

                    // if evaluate to false, push next event 
                    buffer.Append("\t\telse {\n");
                    string falsePath = prevStatePath + label.ToString().ToLower();
                    buffer.Append("\t\t\tthis.State=" + this.StateName + "." + falsePath + ";\n");
                    bool isFalsePathLast = true;
                    foreach (string statePath in statePaths)
                    {
                        if (statePath.StartsWith(falsePath) && statePath.Length > falsePath.Length)
                        {
                            isFalsePathLast = false;
                            break;
                        }
                    }
                    if (isFalsePathLast)
                    {
                        buffer.Append("\t\t\tthis.Done();\n");
                    }
                    else
                    {
                        buffer.Append("\t\t\tRuleResult_" + falsePath + " evtNext=new RuleResult_" +
                                      falsePath + "();\n");
                        buffer.Append("\t\t\tthis.PushEvent(evtNext);\n");
                    }
                    buffer.Append("\t\t}\n"); // end else block

                    // end
                    buffer.Append("\t} // end of try block \n");
                    buffer.Append("\tcatch(Exception ex) {\n");
                    buffer.Append("\tresult=false;");
                    buffer.Append("\t}");
                    buffer.Append("}");

                    if (!methodNameCode.ContainsKey(methodName))
                    {
                        methodNameCode.Add(methodName, buffer.ToString());
                    }
                }
            }

            return methodNameCode;
        }
        #endregion
    }
}
